/**
 *  @file Base64.cpp
 */
#include "Base64.h"
#include "../common/CppMemPool.h"
#include "../common/UtilFuncT.h"

namespace cppflib
{

namespace convert
{

namespace base64
{

//! the base64 characters
PRIVATE const char *st_alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

/**
 *  Used in decode procedure to reversely find the index in the array 'st_alpha'
 *  based on the character passed in.
 *  
 *  @param[in] alpha -- the character to be searched
 *
 *  @return index in the array 'st_alpha'
 */
PRIVATE int __SearchIndex(int alpha)
{           
   if (alpha >= 'a' && alpha <= 'z') {
      return (26 + alpha - static_cast<int>('a'));
   }
   else if (alpha >= 'A' && alpha <= 'Z') {
      return (/* 0 + */ alpha - static_cast<int>('A'));
   }
   else if (alpha >= '0' && alpha <= '9') {
      return (52 + alpha - static_cast<int>('0'));
   }
   else if (alpha == '+') {
      return 62;
   }
   else if (alpha == '/') {
      return 63;
   }            

   // '='
   return 64;
}

/**
 *  Get number of bytes needed for encoding in base64
 *
 *  @return number of bytes needed for encoding
 */
_DLLAPI u32_t GetEncodeBufferNeed(const u32_t srcLen)
{
   if (srcLen == 0)
      return 0;

   u32_t byteNeed = (srcLen + 3 - 1) / 3;
   byteNeed *= 4;

   return byteNeed;
}

/**
 *  Get number of bytes needed for decoding in base64
 *
 *  @return number of bytes needed for decoding
 */
_DLLAPI u32_t GetDecodeBufferNeed(const u8_t * srcBuf, const u32_t srcLen)
{
   if (srcLen == 0)
      return 0;

   // check if srcBuf is 4 byte aligned
   if ((srcLen % 4) != 0)
      return 0;

   // compute nr bytes needed
   u32_t byteNeed = srcLen / 4 * 3;
   if (srcBuf[srcLen - 1] == '=') byteNeed -= 1;
   if (srcBuf[srcLen - 2] == '=') byteNeed -= 1;

   return byteNeed;
}

/**
 *   Encode 3 byte data into 4 byte base64 data
 *
 *   @param[in] numByte -- number of valid byte : 1 -- b1 valid
 *                                                2 -- b1, b2 valid
 *                                                3 -- b1, b2, b3 valid
 *   @param[in] b1 -- 1st data byte
 *   @param[in] b2 -- 2nd data byte
 *   @param[in] b3 -- 3rd data byte
 *   @param[out] out -- buffer to hold the result (make sure it has a buffer of 4 byte !)
 */
_DLLAPI void Encode(const int numByte, const int b1, const int b2, const int b3, u8_t out[4])
{     
   //
   // 1st output byte
   int temp = (b1 >> 2) & 0x3f;
   out[0] = st_alpha[temp];
   //
   // 2nd output byte
   temp = b1 & 0x03;
   temp = temp << 4;
      
   if (numByte == 1) {
      out[1] = st_alpha[temp];
      out[2] = st_alpha[64];
      out[3] = st_alpha[64];
      return;
   }
                       
   int temp2 = b2 >> 4;
   temp = (temp | temp2) & 0x3f;      
   out[1] = st_alpha[temp];
   //
   // 3rd output byte
   temp = b2 & 0x0f;
   temp = temp << 2;
      
   if (numByte == 2) {
      out[2] = st_alpha[temp];
      out[3] = st_alpha[64];
      return;
   }
   temp2 = b3 & 0xc0;
   temp2 = temp2 >> 6;
   temp = (temp | temp2) & 0x3f;
   out[2] = st_alpha[temp];
   //
   // 4th output byte
   temp = b3 & 0x3f;
   out[3] = st_alpha[temp];
}

/**
 *  To decode the 4 6-bit groups to 3 8-bit groups
 *  
 *  @param[in] b1 -- input data byte 
 *  @param[in] b2 -- input data byte
 *  @param[in] b3 -- input data byte (may be '=')
 *  @param[in] b4 -- input data byte (may be '=')
 * 
 *  @param[out] out -- the 3 byte buffer to save the output
 *  @param[out] numDecoded -- number of valid bytes in buffer 'out'
 */
_DLLAPI void Decode(const int b1, const int b2, const int b3, const int b4, u8_t out[3], int * numDecoded)
{
   //
   // 1st byte
   int temp = __SearchIndex(b1);
   temp = temp << 2;
   int temp2 = __SearchIndex(b2);      
   int temp3 = temp2 >> 4;
   temp = temp | temp3;
   out[0] = static_cast<u8_t>(temp);
      
   if (b3 == '=') {
      // don't assign to out[1] and out[2]
      // users should not read these 2 values !
      *numDecoded = 1;
      return;   
   }
   //
   // 2nd byte
   temp = temp2 & 0x0f;
   temp = temp << 4;
   temp2 = __SearchIndex(b3);
   temp3 = temp2 >> 2;
   temp = temp | temp3;
   out[1] = static_cast<u8_t>(temp);
      
   if (b4 == '=') {
      // don't assign to out[2]
      // users should not read this value !
      *numDecoded = 2;
      return;   
   }
    
   //
   // 3rd byte
   temp = temp2 & 0x03;
   temp = temp << 6;
   temp2 = __SearchIndex(b4);
   temp = temp | temp2;
   out[2] = static_cast<u8_t>(temp);

   *numDecoded = 3;
}

/**
 *  A more friendly interface to encode data into base64
 *  
 *  @param[in] srcBuf -- data buffer to encode
 *  @param[in] srcLen -- length of inBuf in byte
 *  @param[in,out] outBuf -- buffer that will hold the encoded data.
 *
 *                        If the dereferenced pointer is non-NULL, it should point to a
 *                        buffer big enough to hold the encoded data.
 *                        Call base64::GetEncodeBufferNeed() to determine buffer size needed.
 *                        Otherwise, a new buffer is allocated and is added to current auto release pool.
 *
 *  @param[in,out] outLen -- if outBuf is non-null, contains size of outBuf.
 *                           on return, number of bytes encoded is returned
 *
 *  @return true -- OK
 *  @return false -- error
 */
_DLLAPI bool Encode(const u8_t * srcBuf, const int srcLen, u8_t ** outBuf, int * outLen)
{
   int byteNeed, i, numByte;
   u8_t * pBuf = NULL; 
   
   // determine buffer needed
   if ((byteNeed = static_cast<int>(GetEncodeBufferNeed(srcLen))) <= 0)
      return false;
    
   if (*outBuf) {
      // user provided buffer
      pBuf = *outBuf;
      if (*outLen < byteNeed)
         return false;
   }
   else {
      pBuf = new (cppmempool::NEWPTR) u8_t[byteNeed];
   }

   // assign value for return
   *outBuf = pBuf;
   *outLen = byteNeed;

   // main loop
   for (i = 0; i < srcLen; i += 3, pBuf += 4) {

      numByte = srcLen - i;
      numByte = cppflib::Min<int>(numByte, 3);
      if (numByte == 3) {
         Encode(numByte, srcBuf[i], srcBuf[i + 1], srcBuf[i + 2], pBuf);           
      }
      else if (numByte == 2) {
         Encode(numByte, srcBuf[i], srcBuf[i + 1], 0, pBuf);           
      }
      else if (numByte == 1) {
         Encode(numByte, srcBuf[i], 0, 0, pBuf);           
      }      
                
   }

   return true;
}

/**
 *  A more friendly interface to decode base64 data
 *  
 *  @param[in] srcBuf -- data buffer to decode
 *  @param[in] srcLen -- length of inBuf in byte
 *  @param[in,out] outBuf -- buffer that will hold the decoded data
 *
 *                        If the dereferenced pointer is non-NULL, it should point to a
 *                        buffer big enough to hold the decoded data.
 *                        Call GetDecodeBufferNeed() to determine buffer size needed.
 *                        Otherwise, a new buffer is allocated and is added to current auto release pool.
 *
 *  @param[in,out] outLen -- if outBuf is non-null, contains size of outBuf.
 *                           on return, number of bytes decoded is returned
 *
 *  @return true -- OK
 *  @return false -- error
 */
_DLLAPI bool Decode(const u8_t * srcBuf, const int srcLen, u8_t ** outBuf, int * outLen)
{
   int byteNeed, i, numDecoded;
   u8_t * pBuf = NULL; 

   // determine buffer needed
   if ((byteNeed = static_cast<int>(GetDecodeBufferNeed(srcBuf, srcLen))) <= 0)
      return false;
    
   if (*outBuf) {
      // user provided buffer
      pBuf = *outBuf;
      if (*outLen < byteNeed)
         return false;
   }
   else {
      pBuf = new (cppmempool::NEWPTR) u8_t[byteNeed];
   }

   // assign value for return
   *outBuf = pBuf;
   *outLen = byteNeed;

   // main loop
   for (i = 0; i < srcLen; i += 4, pBuf += 3) {
      Decode(srcBuf[i], srcBuf[i + 1], srcBuf[i + 2], srcBuf[i + 3], pBuf, &numDecoded);             
   }

   return true;
}


} // end of base64

} // end of convert

} // end of cppflib


